package small.common.designPattern.behavioral.observer.publisher;

import org.slf4j.Logger;
import small.common.designPattern.behavioral.observer.event.Event;
import small.common.designPattern.behavioral.observer.subscriber.Subscriber;
import small.common.log.LogProvider;

import java.util.Map;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author jinzhou
 * @data 2023/5/9 18:56
 */
public class DefaultPublisher extends Thread implements EventPublisher {

    protected final Logger logger = LogProvider.getLogger(getClass());

    /**
     * 初始化标识
     */
    private AtomicBoolean initialized = new AtomicBoolean(false);

    /**
     * 停用标识
     */
    private transient boolean shutdown = false;

    /**
     * 事件类型
     */
    private Class<? extends Event> eventType;

    /**
     * 订阅者
     */
    protected final Map<Subscriber, Boolean> subscribers = new ConcurrentHashMap();

    /**
     * 阻塞队列最大长度
     */
    private int queueMaxSize = -1;

    /**
     * 事件阻塞队列
     */
    private BlockingQueue<Event> queue;

    /**
     * 最后一个事件序列号
     */
    protected volatile String lastEventSequence = null;

    public DefaultPublisher(Class<? extends Event> type, String publisherName, int bufferSize) {
        setDaemon(true);
        setName(publisherName + type.getName());
        this.eventType = type;
        this.queueMaxSize = bufferSize;
        if (queueMaxSize < 0) {
            queueMaxSize = 1024;
        }
        this.queue = new ArrayBlockingQueue<Event>(bufferSize);
        start();
    }

    public Set<Subscriber> getSubscribers() {
        return subscribers.keySet();
    }

    @Override
    public synchronized void start() {
        if (initialized.compareAndSet(false, true)) {
            super.start();
        }
    }

    @Override
    public long currentEventSize() {
        return queue.size();
    }

    @Override
    public void run() {
        openEventHandler();
    }

    void openEventHandler() {
        try {
            //为了解决消息在队列中积压的问题(没有订阅者 但是一直有消息进来)
            int waitTimes = 60;

            for (; ; ) {
                if (shutdown || hasSubscriber() || waitTimes <= 0) {
                    break;
                }
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
                waitTimes--;
            }

            for (; ; ) {
                if (shutdown) {
                    break;
                }
                final Event event = queue.take();
                receiveEvent(event);
                this.lastEventSequence = event.sequence();
            }
        } catch (Throwable ex) {
            logger.error("Event listener exception : ", ex);
        }
    }

    @Override
    public void addSubscriber(Subscriber subscriber) {
        subscribers.putIfAbsent(subscriber, Boolean.TRUE);
    }

    @Override
    public void removeSubscriber(Subscriber subscriber) {
        subscribers.remove(subscriber);
    }

    @Override
    public boolean publish(Event event) {
        checkIsStart();
        boolean success = this.queue.offer(event);
        if (!success) {
            logger.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
            receiveEvent(event);
            return true;
        }
        return true;
    }

    void checkIsStart() {
        if (!initialized.get()) {
            throw new IllegalStateException("Publisher does not start");
        }
    }

    private boolean hasSubscriber() {
        return subscribers.size() > 0;
    }

    @Override
    public void shutdown() {
        this.shutdown = true;
        this.queue.clear();
    }

    /**
     * 通知订阅者接收事件
     *
     * @param event {@link Event}.
     */
    void receiveEvent(Event event) {

        boolean noSubscriberFlag = true;

        for (Subscriber subscriber : subscribers.keySet()) {
            if (!subscriber.scopeMatches(event)) {
                continue;
            }
            //通知订阅者
            notifySubscriber(subscriber, event);
            noSubscriberFlag = false;
        }

        if (noSubscriberFlag) {
            logger.debug("[{}] the sequence {} event{} will take , but not Subscriber accept", getClass().getName(), event.sequence(), event);
        }
    }

    @Override
    public void notifySubscriber(final Subscriber subscriber, final Event event) {

        logger.debug("[{}] the sequence {} event{} will received by {}", getClass().getName(), event.sequence(), event, subscriber);

        final Runnable job = new Runnable() {
            @Override
            public void run() {
                subscriber.onEvent(event);
            }
        };

        final Executor executor = subscriber.executor();

        if (executor != null) {
            executor.execute(job);
        } else {
            try {
                job.run();
            } catch (Throwable e) {
                logger.error("Event callback exception : ", e);
            }
        }
    }

}
